package com.example.demo.use_demo.a_excel_demo.listener;

import cn.hutool.core.util.ReflectUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.exception.ExcelAnalysisStopException;
import com.alibaba.excel.read.metadata.holder.xlsx.XlsxReadSheetHolder;
import com.example.demo.use_demo.a_excel_demo.ExcelDataConverter;
import com.example.demo.use_demo.a_excel_demo.ExcelDataValidator;
import com.example.demo.use_demo.a_excel_demo.entity.ExcelImportResult;
import com.example.demo.exception.BusinessException;
import lombok.extern.slf4j.Slf4j;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * 场景：Domain 通用
 * EasyExcel
 * <p>
 * 适用场景：用于大数据量且验证信息需要比较详细的场景  其他数量不大的场景可使用ExcelReaderExtHuTool
 * 终止导入情况：
 * 以 BATCH_COUNT 为批次加入事务控制, 当某一批次出
 * 现数据验证失败则停止导入, 当前批次的验证失败不影响
 * 之前已经成功持久化的批次数据。
 *
 * @author zhangqiang
 * @tip 如果用于验证的基础数据量过大
 * 如果导入的数据量过大，同时需要数据的全量唯一验证，此时也可考虑分批在数据库验证
 * 如果要验证的数据量不大 可用此工具验证可以分批在数据库验证
 * @date 2020/9/7
 */
@Slf4j
public class ExcelDomainListener<D> extends AnalysisEventListener<D> {

    /**
     * 每批次读取的数据量
     */
    private static final int CYCLE_COUNT = 10;
    /**
     * 当前批次已读取数据量
     */
    private int cycleReadCount = 0;
    /**
     * 当前批次出现过解析或者验证失败
     */
    private boolean cycleFailedExist = false;
    /**
     * 当前批次读取的数据列表
     */
    private List<D> cycleDataList = new ArrayList<>();
    /**
     * 导入结果
     */
    public ExcelImportResult excelImportResult;
    /**
     * 数据转换器
     */
    private ExcelDataConverter<D> excelDataConverter;
    /**
     * 数据验证器
     */
    private ExcelDataValidator<D> excelDataValidator;
    /**
     * 行号
     */
    private int rowIndex = 0;
    /**
     * 业务类
     */
    private Class<?> saveDataMethodClass;
    /**
     * 业务类数据保存方法
     */
    private String saveDataMethodName;

    public ExcelDomainListener(Class<?> saveDataMethodClass, String saveDataMethodName,
                               ExcelDataConverter<D> excelDataConverter, ExcelDataValidator<D> excelDataValidator) {
        this.excelImportResult = new ExcelImportResult();
        this.saveDataMethodClass = saveDataMethodClass;
        this.saveDataMethodName = saveDataMethodName;
        this.excelDataConverter = excelDataConverter;
        this.excelDataValidator = excelDataValidator;
    }

    public ExcelDomainListener(Class<?> saveDataMethodClass, String saveDataMethodName,
                               ExcelDataConverter<D> excelDataConverter) {
        this.excelImportResult = new ExcelImportResult();
        this.saveDataMethodClass = saveDataMethodClass;
        this.saveDataMethodName = saveDataMethodName;
        this.excelDataConverter = excelDataConverter;
    }

    public ExcelDomainListener(Class<?> saveDataMethodClass, String saveDataMethodName,
                               ExcelDataValidator<D> excelDataValidator) {
        this.excelImportResult = new ExcelImportResult();
        this.saveDataMethodClass = saveDataMethodClass;
        this.saveDataMethodName = saveDataMethodName;
        this.excelDataValidator = excelDataValidator;
    }

    public ExcelDomainListener(Class<?> saveDataMethodClass, String saveDataMethodName) {
        this.excelImportResult = new ExcelImportResult();
        this.saveDataMethodClass = saveDataMethodClass;
        this.saveDataMethodName = saveDataMethodName;
    }

    /**
     * 行数据解析(默认忽略空行)
     *
     * @param data    行数据
     * @param context 行上下文
     */
    @Override
    public void invoke(D data, AnalysisContext context) {
        rowIndex = context.readRowHolder().getRowIndex() + 1;
        cycleReadCount++;
        try {
            // 转换
            if (excelDataConverter != null) {
                excelDataConverter.convertFields(data);
            }
            // 验证
            String resultMsg = null;
            if (excelDataValidator != null) {
                resultMsg = excelDataValidator.validateFields(data);
            }

            if (resultMsg != null) {
                cycleFailedExist = true;
                excelImportResult.getErrorMsgList().add(String.format("第%s行:%s", rowIndex, resultMsg));
            } else {
                cycleDataList.add(data);
            }
        } catch (Exception e) {
            cycleFailedExist = true;
            if (e instanceof BusinessException) {
                excelImportResult.getErrorMsgList().add(String.format("第%s行:%s", rowIndex, e.getMessage()));
            } else {
                excelImportResult.getErrorMsgList().add(String.format("第%s行:%s", rowIndex, "数据解析失败"));
                log.error("数据解析失败",e);
            }
        }
        // 批次数据保存
        if (cycleReadCount >= CYCLE_COUNT) {
            this.doSaveData();
        }
    }

    /**
     * 表头分析
     */
    @Override
    public void invokeHeadMap(Map<Integer, String> headMap, AnalysisContext context) {
        // 表头验证
        // ...
    }

    @Override
    public void doAfterAllAnalysed(AnalysisContext context) {
        // 遗留数据保存
        this.doSaveData();
    }

    @Override
    public void onException(Exception exception, AnalysisContext context) throws Exception {
        // 数据终止读取异常
        if (exception instanceof ExcelAnalysisStopException) {
            throw exception;
            // excel数据读取成对象之前的异常
        } else {
            String tempCellName = ((XlsxReadSheetHolder) context.currentReadHolder()).getTempCellData().getStringValue();
            cycleReadCount++;
            cycleFailedExist = true;
            excelImportResult.getErrorMsgList().add(String.format("第%s行:%s数据读取失败", context.readRowHolder().getRowIndex() + 1, tempCellName));
            if (cycleReadCount >= CYCLE_COUNT) {
                cycleDataList.clear();
                throw new ExcelAnalysisStopException();
            }
        }
    }

    /**
     * 批次数据保存
     */
    private void doSaveData() {
        // 如果当前批次出现了解析异常则停止数据保存
        if (cycleFailedExist) {
            cycleDataList.clear();
            throw new ExcelAnalysisStopException();
        }
        // 批次数据保存
        try {
            Method method = ReflectUtil.getMethod(this.saveDataMethodClass, this.saveDataMethodName, List.class, ExcelImportResult.class);
            method.invoke(this.saveDataMethodClass.newInstance(), cycleDataList,excelImportResult);
            excelImportResult.setImportSuccessEndRowNum(rowIndex);
            cycleReadCount = 0;
        } catch (Exception e) {
            String errorMsg = String.format("第%s行至%s行:%s", excelImportResult.getImportSuccessEndRowNum() + 1, rowIndex, e instanceof BusinessException ? e.getMessage() : "数据保存失败");
            excelImportResult.getErrorMsgList().add(errorMsg);
            log.error("数据保存失败",e);
            throw new ExcelAnalysisStopException();
        } finally {
            cycleDataList.clear();
        }
    }
}
